package com.nulldev.util.image;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferInt;
import java.awt.image.DataBufferShort;
import java.awt.image.DataBufferUShort;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;

import javax.imageio.ImageIO;
import javax.swing.Icon;
import javax.swing.ImageIcon;

import com.nulldev.util.FinalBypass;
import com.nulldev.util.encoding.base64.Base64;
import com.nulldev.util.java.utils.color.ColorUtil;
import com.nulldev.util.logging.Logger;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.math.MathUtil;
import com.nulldev.util.other.ByteUtils;
import com.nulldev.util.threading.legacy.ThreadExecutor;

public class ImageUtil {

	public static final BufferedImage BLANK = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);

	public static BufferedImage scale(BufferedImage imageToScale, int dWidth, int dHeight) {
        BufferedImage scaledImage = null;
        if (imageToScale != null) {
            scaledImage = new BufferedImage(dWidth, dHeight, imageToScale.getType());
            Graphics2D graphics2D = scaledImage.createGraphics();
            graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            graphics2D.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
            graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            graphics2D.drawImage(imageToScale, 0, 0, dWidth, dHeight, null);
            graphics2D.dispose();
        }
        return scaledImage;
    }
	
    public static Image scale(Image imageToScale, int dWidth, int dHeight) {
        Image scaledImage = null;
        if (imageToScale != null) {
            scaledImage = new BufferedImage(dWidth, dHeight, BufferedImage.TYPE_INT_RGB);
            Graphics2D graphics2D = (Graphics2D) scaledImage.getGraphics();
            graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            graphics2D.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
            graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            graphics2D.drawImage(imageToScale, 0, 0, dWidth, dHeight, null);
            graphics2D.dispose();
        }
        return scaledImage;
    }
	
	public static ImageIcon scale(final ImageIcon imageToScale, int dWidth, int dHeight) {
		//BufferedImage imageToScale = AWTimageToBufferedImage((ToolkitImage) image.getImage());
        BufferedImage scaledImage = null;
        if (imageToScale != null) {
            scaledImage = new BufferedImage(dWidth, dHeight, imageToBufferedImage(imageToScale.getImage()).getType());
            Graphics2D graphics2D = scaledImage.createGraphics();
            graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            graphics2D.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
            graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            graphics2D.drawImage(imageToBufferedImage(imageToScale.getImage()), 0, 0, dWidth, dHeight, null);
            graphics2D.dispose();
        }
        return new ImageIcon(scaledImage);
    }
	
	public static BufferedImage AWTimageToBufferedImage(final sun.awt.image.ToolkitImage im) {
		BufferedImage bi = new BufferedImage(im.getWidth(), im.getHeight(), BufferedImage.TYPE_INT_RGB);
		Graphics bg = bi.getGraphics();
		bg.drawImage(im, 0, 0, null);
		bg.dispose();
		return bi;
	}
	
	public static BufferedImage imageToBufferedImage(final Image im) {
		BufferedImage bi = new BufferedImage(im.getWidth(null), im.getHeight(null), BufferedImage.TYPE_INT_RGB);
		Graphics bg = bi.getGraphics();
		bg.drawImage(im, 0, 0, null);
		bg.dispose();
		return bi;
	}

	public static ImageIcon createFromColor(final Color color, final int w, final int h) {
		final BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
		final Graphics2D f = (Graphics2D) img.getGraphics();
		f.setColor(color);
		for (int x = 0; x < w; x++) {
			for (int y = 0; y < h; y++) {
				f.drawRect(x, y, 1, 1);
				f.fillRect(x, y, 1, 1);
			}
		}
		return new ImageIcon(img);
	}

	public static BufferedImage createFromColorImg(final Color color, final int w, final int h) {
		final BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
		final FinalBypass<Boolean> done = new FinalBypass<Boolean>(false);
		ThreadExecutor.execute(new Runnable() {

			@Override
			public void run() {
				final Graphics2D f = (Graphics2D) img.getGraphics();
				f.setColor(color);
				for (int x = 0; x < w; x++) {
					for (int y = 0; y < h; y++) {
						f.drawRect(x, y, 1, 1);
						f.fillRect(x, y, 1, 1);
					}
				}
				done.setObject(true);
			}
		});
		while (!done.getObject()) {
			try {
				Thread.sleep(10L);
			} catch (InterruptedException e) {
			}
		}
		return img;
	}
	
	public static BufferedImage random(final int x, final int y) {
		return createFromColorImg(new Color(MathUtil.nextRandomInt()), x, y);
	}

	public static ByteBuffer convertImageData(final BufferedImage bi) {
	    final ByteArrayOutputStream out = new ByteArrayOutputStream();
	    try {
	        ImageIO.write(bi, "png", out);
	        return ByteBuffer.wrap(out.toByteArray());
	    } catch (IOException ex) {
	    }
	    return ByteBuffer.allocate(0);
	}
	
	public static String convertImageDataBase64(final BufferedImage bi) {
	    ByteArrayOutputStream out = new ByteArrayOutputStream();
	    try {
	        ImageIO.write(bi, "png", out);
	        return ByteUtils.toBase64(out.toByteArray());
	    } catch (IOException ex) {
	    }
	    return "";
	}

	public static ByteBuffer convertImageDataI(final BufferedImage img) {
		final ByteBuffer byteBuffer;
		final DataBuffer dataBuffer = img.getRaster().getDataBuffer();

		if (dataBuffer instanceof DataBufferByte) {
		    byte[] pixelData = ((DataBufferByte) dataBuffer).getData();
		    byteBuffer = ByteBuffer.wrap(pixelData);
		}
		else if (dataBuffer instanceof DataBufferUShort) {
		    short[] pixelData = ((DataBufferUShort) dataBuffer).getData();
		    byteBuffer = ByteBuffer.allocate(pixelData.length * 2);
		    byteBuffer.asShortBuffer().put(ShortBuffer.wrap(pixelData));
		}
		else if (dataBuffer instanceof DataBufferShort) {
		    short[] pixelData = ((DataBufferShort) dataBuffer).getData();
		    byteBuffer = ByteBuffer.allocate(pixelData.length * 2);
		    byteBuffer.asShortBuffer().put(ShortBuffer.wrap(pixelData));
		}
		else if (dataBuffer instanceof DataBufferInt) {
		    int[] pixelData = ((DataBufferInt) dataBuffer).getData();
		    byteBuffer = ByteBuffer.allocate(pixelData.length * 4);
		    byteBuffer.asIntBuffer().put(IntBuffer.wrap(pixelData));
		}
		else {
		    throw new IllegalArgumentException("Not implemented for data buffer type: " + dataBuffer.getClass());
		}
		return byteBuffer;
	}

	public static ByteBuffer convertImageDataX(final BufferedImage image) {
		int[] pixels = new int[image.getWidth() * image.getHeight()];
		image.getRGB(0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth());

		final ByteBuffer buffer = ByteBuffer.allocate(image.getWidth() * image.getHeight() * 3);

		for (int y = 0; y < image.getHeight(); y++) {
			for (int x = 0; x < image.getWidth(); x++) {
				int pixel = pixels[y * image.getWidth() + x];
				buffer.put((byte) ((pixel >> 16) & 0xFF));
				buffer.put((byte) ((pixel >> 8) & 0xFF));
				buffer.put((byte) (pixel & 0xFF));
			}
		}

		buffer.flip();
		return buffer;
	}

	public static BufferedImage toImage(final Icon icon) {
		final BufferedImage bi = new BufferedImage(icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_INT_RGB);
		final Graphics g = bi.createGraphics();
		icon.paintIcon(null, g, 0,0);
		g.dispose();
		return bi;
	}

	public static BufferedImage transparent(final BufferedImage image, final float alpha) {
		  final BufferedImage out = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB);
		  final Graphics2D g2d = (Graphics2D) out.getGraphics();
		  g2d.setComposite(AlphaComposite.SrcOver.derive(MathUtil.clamp(0f, 1f, alpha)));
		  g2d.drawImage(image, 0, 0, null);
		  g2d.dispose();
		  return out;
	}

	public static ImageIcon toIcon(final BufferedImage image) {
		return new ImageIcon(image);
	}
	
	public static BufferedImage B2W(final BufferedImage in) {
		final BufferedImage out = new BufferedImage(in.getWidth(), in.getHeight(), in.getType());
		final Graphics2D g2d_in = (Graphics2D) in.getGraphics();
		final Graphics2D g2d_out = (Graphics2D) out.getGraphics();
		final FinalBypass<Boolean> done = new FinalBypass<Boolean>(false);
		/**
		 * 		for (int x = 0; x < in.getWidth(); x++) {
			for (int y = 0; y < in.getHeight(); y++) {
				g2d_out.setColor(new Color(in.getRGB(x, y)));
				g2d_out.drawRect(x, y, 1, 1);
				g2d_out.fillRect(x, y, 1, 1);
			}
			}
		}
		 */
		for (int x = 0; x < in.getWidth(); x++) {
			for (int y = 0; y < in.getHeight(); y++) {
				final boolean swap = in.getRGB(x, y) == Color.black.getRGB();
				if (swap) {
					g2d_out.setColor(Color.WHITE);
				}else {
					final int rgb = in.getRGB(x, y);
					if (rgb == 0) {
						g2d_out.setColor(ColorUtil.TRANSPARENT);
					}else {
						g2d_out.setColor(new Color(rgb));
					}
				}
				if (g2d_out.getColor() != ColorUtil.TRANSPARENT) {
					g2d_out.fillRect(x, y, 1, 1);
				}
			}
		}
		return out;
	}
	
	public static BufferedImage B2A(final BufferedImage in, final Color color) {
		final BufferedImage out = new BufferedImage(in.getWidth(), in.getHeight(), in.getType());
		final Graphics2D g2d_in = (Graphics2D) in.getGraphics();
		final Graphics2D g2d_out = (Graphics2D) out.getGraphics();
		final FinalBypass<Boolean> done = new FinalBypass<Boolean>(false);
		for (int x = 0; x < in.getWidth(); x++) {
			for (int y = 0; y < in.getHeight(); y++) {
				final boolean swap = in.getRGB(x, y) == Color.black.getRGB();
				if (swap) {
					g2d_out.setColor(color);
				}else {
					final int rgb = in.getRGB(x, y);
					if (rgb == 0) {
						g2d_out.setColor(ColorUtil.TRANSPARENT);
					}else {
						g2d_out.setColor(new Color(rgb));
					}
				}
				if (g2d_out.getColor() != ColorUtil.TRANSPARENT) {
					g2d_out.fillRect(x, y, 1, 1);
				}
			}
		}
		return out;
	}

	public static String encodeToString(BufferedImage image, String type) {
        String imageString = null;
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
 
        try {
            ImageIO.write(image, type, bos);
            byte[] imageBytes = bos.toByteArray();
 
            Base64 encoder = new Base64();
            imageString = new String(encoder.encode(imageBytes));
 
            bos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return imageString;
    }

	public static Image getLogo32() {
		try {
			return ImageUtil.scale(ImageIO.read(ImageUtil.class.getResource("/com/nulldev/util/java/swing/nu_logo.png")), 32, 32);
		}catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException("Failed to get image!");
		}
	}
	
	public static Image getLogo24() {
		try {
			return ImageUtil.scale(ImageIO.read(ImageUtil.class.getResource("/com/nulldev/util/java/swing/nu_logo.png")), 24, 24);
		}catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException("Failed to get image!");
		}
	}

	public static BufferedImage safeRead(final URL url) {
		if (url == null) {
			return BLANK;
		}
		try {
			return ImageIO.read(url);
		}catch (Exception e) {
			return BLANK;
		}
	}

	public static int scaleResW(final BufferedImage image, final int width) {
		final float imgR = image.getWidth() / image.getHeight();
		if (imgR > 1) {
			return image.getWidth();
		}
		int out = 0;
		float factor = 0.1f;
		while (true) {
			int oX = (int) (image.getWidth() * factor);
			out = oX;
			factor += 0.1f;
			if (out > width) {
				break;
			}
		}
		return out;
	}
	
	public static int scaleResH(final BufferedImage image, final int height) {
//		final double ratio = image.getHeight() / height;
//		if (ratio <= 0) {
//			return image.getHeight();
//		}
//		return (int) (image.getHeight() / ratio);
		final double imgR = image.getWidth() / image.getHeight(); //image.getHeight() / image.getWidth();
		if (imgR > 1) {
			return image.getHeight();
		}
		int out = 0;
		float factor = 0.1f;
		while (true) {
			out = (int) (image.getHeight() * factor);
			factor += 0.1f;
			if (out > height) {
				break;
			}
		}
		return out;
	}
	
	public static BufferedImage useIUS(final BufferedImage image, final int scaledW, final int scaledH) {
		return scale(image, scaleResW(image, scaledW), scaleResH(image, scaledH));
	}

	public static Image createFromColorRndImg(final int w, final int h) {
		final BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
		final FinalBypass<Boolean> done = new FinalBypass<Boolean>(false);
		ThreadExecutor.execute(new Runnable() {

			@Override
			public void run() {
				final Graphics2D f = (Graphics2D) img.getGraphics();
				for (int x = 0; x < w; x++) {
					for (int y = 0; y < h; y++) {
						f.setColor(ColorUtil.randomColor());
						f.drawRect(x, y, 1, 1);
						f.fillRect(x, y, 1, 1);
					}
				}
				done.setObject(true);
			}
		});
		while (!done.getObject()) {
			try {
				Thread.sleep(10L);
			} catch (InterruptedException e) {
			}
		}
		return img;
	}
	
	private static final Logger log = LoggerFactory.getLogger();
	
	public static BufferedImage createImageFromBytes(byte[] imageData) {
		if (imageData == null) {
			log.warn("createImageFromBytes(imageData): imageData == null!");
		}
	    final ByteArrayInputStream bais = new ByteArrayInputStream(imageData);
	    try {
	        return ImageIO.read(bais);
	    } catch (IOException e) {
	        throw new RuntimeException(e);
	    }
	}
	
	public static Color averageColor(BufferedImage bi, int x0, int y0, int w, int h) {
	    int x1 = x0 + w;
	    int y1 = y0 + h;
	    long sumr = 0, sumg = 0, sumb = 0;
	    for (int x = x0; x < x1; x++) {
	        for (int y = y0; y < y1; y++) {
	            Color pixel = new Color(bi.getRGB(x, y));
	            sumr += pixel.getRed();
	            sumg += pixel.getGreen();
	            sumb += pixel.getBlue();
	        }
	    }
	    int num = w * h;
	    return new Color((int) MathUtil.clamp(0, 255, sumr / num), (int) MathUtil.clamp(0, 255, sumg / num), (int) MathUtil.clamp(0, 255, sumb / num));
	}
	
	public static Color averageColor(BufferedImage bi, int w, int h) {
	    long sumr = 0, sumg = 0, sumb = 0;
	    for (int x = 0; x < w; x++) {
	        for (int y = 0; y < h; y++) {
	            Color pixel = new Color(bi.getRGB(x, y));
	            sumr += pixel.getRed();
	            sumg += pixel.getGreen();
	            sumb += pixel.getBlue();
	        }
	    }
	    int num = w * h;
	    return new Color((int) MathUtil.clamp(0, 255, sumr / num), (int) MathUtil.clamp(0, 255, sumg / num), (int) MathUtil.clamp(0, 255, sumb / num));
	}
}
